﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using OverStore.Runtime.ObjectRepository.CallbackManager;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Configuration.ObjectRepository.CallbackManager;

namespace OverStore.Configuration.Builder.Fluent
{
    /// <summary>
    /// Fluent builder for object structure descritor.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TNextStep">The type of the next step.</typeparam>
    public static class ObjectStructure<TInstance, TNextStep>
        where TInstance : class
    {
        /// <summary>
        /// Parameters required for fluent build.
        /// </summary>
        public class Params
        {
            private readonly Func<IEnumerable<IComponentConfiguration<ICallbackManager<TInstance>>>, TNextStep> _OnCompleted;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectStructure&lt;TInstance, TNextStep&gt;.Params"/> class.
            /// </summary>
            public Params(Func<IEnumerable<IComponentConfiguration<ICallbackManager<TInstance>>>, TNextStep> onCompleted)
            {
                if (Object.ReferenceEquals(onCompleted, null))
                    throw new ArgumentNullException("onCompleted");
                _OnCompleted = onCompleted;
                Hierarchy = new List<IComponentConfiguration<ICallbackManager<TInstance>>>();
            }

            /// <summary>
            /// Gets or sets the hierarchy.
            /// </summary>
            /// <value>The hierarchy.</value>
            public List<IComponentConfiguration<ICallbackManager<TInstance>>> Hierarchy { get; private set; }

            /// <summary>
            /// Completes this instance.
            /// </summary>
            /// <returns></returns>
            public TNextStep Complete()
            {
                return _OnCompleted(Hierarchy);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public class ObjectStructureStep
        {
            private readonly Params _Parameters;

            /// <summary>
            /// Initializes a new instance of the <see cref="ObjectStructure&lt;TInstance, TNextStep&gt;.ObjectStructureStep"/> class.
            /// </summary>
            /// <param name="parameters">The parameters.</param>
            public ObjectStructureStep(Params parameters)
            {
                if (Object.ReferenceEquals(parameters, null))
                    throw new ArgumentNullException("parameters");
                _Parameters = parameters;
            }

            /// <summary>
            /// Maintain reference to collection of object that
            /// related to the instance by one-to-many relation where
            /// instance is on 'one' relation side.
            /// </summary>
            /// <typeparam name="TAggregate">Type of elements in aggregate collection.</typeparam>
            /// <param name="getAggregate">Get aggregate collection.</param>
            /// <param name="setAggregate">Set aggregate collection to instance or null.</param>
            /// <param name="restoreReference">Delegate that restores reference to parent in aggregate instance.</param>
            /// <param name="useInstanceAsParamOnAggregateReloading"><c>true</c> if aggregate collection
            /// will be reloaded with passing instance as parameter.</param>
            /// <returns></returns>
            public ObjectStructureStep CollectionAggregate<TAggregate>(
                Func<TInstance, IEnumerable<TAggregate>> getAggregate,
                Action<TInstance, IEnumerable<TAggregate>> setAggregate,
                Action<TInstance, TAggregate, IPersistenceServiceProvider> restoreReference,
                bool useInstanceAsParamOnAggregateReloading)
                where TAggregate : class
            {
                if (Object.ReferenceEquals(getAggregate, null))
                    throw new ArgumentNullException("getAggregate");
                if (Object.ReferenceEquals(restoreReference, null))
                    throw new ArgumentNullException("restoreReference");

                _Parameters.Hierarchy.Add(
                    new CollectionAggregateConfiguration<TInstance, TAggregate>
                        {
                            GetCollectionAggregate = getAggregate,
                            SetCollectionAggregate = setAggregate,
                            RestoreRelation = restoreReference,
                            UseInstanceAsReloadParameter = useInstanceAsParamOnAggregateReloading
                        });
                return this;
            }

            /// <summary>
            /// Maintain reference to collection of object that 
            /// related to the instance by one-to-many relation where
            /// instance is on 'one' relation side.
            /// </summary>
            /// <typeparam name="TAggregate">Type of elements in aggregate collection.</typeparam>
            /// <param name="getAggregate">Get aggregate collection.</param>
            /// <param name="useInstanceAsParamOnAggregateReloading"><c>true</c> if aggregate collection 
            /// <param name="restoreReference">Delegate that restores reference to parent in aggregate instance.</param>
            /// will be reloaded with passing instance as parameter.</param>
            public ObjectStructureStep CollectionAggregate<TAggregate>(
                Func<TInstance, IEnumerable<TAggregate>> getAggregate,
                Action<TInstance, TAggregate, IPersistenceServiceProvider> restoreReference,
                bool useInstanceAsParamOnAggregateReloading)
                where TAggregate : class
            {
                return CollectionAggregate<TAggregate>(getAggregate, null, restoreReference, useInstanceAsParamOnAggregateReloading);
            }

            /// <summary>
            /// Maintain reference to collection of object that 
            /// related to the instance by one-to-many relation where
            /// instance is on 'one' relation side. Aggregate collection will be reloaded 
            /// with passing instance as parameter.
            /// </summary>
            /// <typeparam name="TAggregate">Type of elements in aggregate collection.</typeparam>
            /// <param name="getAggregate">Get aggregate collection.</param>
            /// <param name="restoreReference">Delegate that restores reference to parent in aggregate instance.</param>
            public ObjectStructureStep CollectionAggregate<TAggregate>(
                            Func<TInstance, IEnumerable<TAggregate>> getAggregate,
                            Action<TInstance, TAggregate, IPersistenceServiceProvider> restoreReference)
                where TAggregate : class
            {
                return CollectionAggregate<TAggregate>(getAggregate, null, restoreReference, true);
            }

            /// <summary>
            /// Ends the hierarchy description.
            /// </summary>
            /// <returns></returns>
            public TNextStep EndHierarchy()
            {
                return _Parameters.Complete();
            }
        }
    }
}
